# Table of Contents

# Spring Data Redis

Spring Data Redis는 스프링 프레임워크에서 redis에 접근할 수 있도록 추상화된 방법을 제공하는 모듈이다.

# 의존성 추가

Spring Data Redis를 사용하려면 다음 의존성을 추가해야한다.

// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

# 환경 설정

application.properties 파일에 설정 값을 추가한다.

spring.redis.host=localhost
spring.redis.port=6379 

다음과 같은 설정값들이 있다.

설정값 기본값 설명
spring.redis.host localhost 레디스 서버 호스트
spring.redis.port 6379 레디스 서버 포트
spring.redis.password 레디스 서버 로그인 패스워드
spring.redis.database 0 커넥션 팩토리에 사용되는 데이터베이스 인덱스
spring.redis.timeout 0 호스트: 포트 쌍 목록 (콤마로 구분)
spring.redis.pool.max-active 8 pool에 할당될 수 있는 커넥션 최대수 (음수로 하면 무제한)
spring.redis.pool.max-idle 8 pool의 "idle" 커넥션 최대수 (음수로 하면 무제한)
spring.redis.pool.max-wait -1 pool이 바닥났을 때 예외 발생 전, 커넥션 할당 차단 최대 시간 (단위 밀리세컨드, 음수는 무제한 차단)
spring.redis.pool.min-idle 0 풀에서 관리하는 idle 커넥션의 쵀소수 대상 (양수일 때만 유효)
spring.redis.sentinel.master 레디스 서버 이름
spring.redis.sentinel.nodes 호스트: 포트 쌍 목록 (콤마로 구분)

그 다음 Spring Data Redis와 관련된 설정 클래스를 추가한다.

@Configuration
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;
}

Redis 서버에 접근하려면 Redis Client를 설정해야한다. 자주 사용되는 Redis Client는 Jedis, Lettuce가 있으며 Lettuce를 더 많이 사용하는 추세인 것 같다. 보통 Redis Client는 스프링 설정 파일에 다음과 같이 빈으로 등록하여 사용한다.










 
 
 
 


@Configuration
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(host, port);
    }
}

# CRUD 작업 실행하기

Spring Data Redis는 Redis에 접근하기 위한 두 가지 방법을 제공한다.

  • RedisTemplate
  • RedisRepository

# RedisTemplate

RedisTemplate는 Redis에 접근하기 위한 헬퍼 클래스다. RedisTemplate을 사용하려면 적절한 설정 후 빈으로 등록해야한다.

@Configuration
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(host, port);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        return redisTemplate;
    }
}

RedisTemplate클래스의 opsForXXX() 메소드를 사용하면 Redis 데이터 타입의 데이터를 저장, 변경, 조회, 삭제할 수 있다.

# opsForValue()

opsForValue()를 사용하면 Redis String 타입의 데이터를 저장, 변경, 조회, 삭제할 수 있다.

@DataRedisTest
class Test {

    @Autowired
    RedisTemplate redisTemplate;

    @Test
    public void test() {
        ValueOperations<String, String> operation = redisTemplate.opsForValue();
        operation.set("name", "Cristiano Ronaldo");

        String saved = operation.get("name");   // "Cristiano Ronaldo"
        assertThat(saved).isEqualTo("Cristiano Ronaldo");
    }
}

# opsForList()

opsForList()를 사용하면 Redis List 타입의 데이터를 저장, 변경, 조회, 삭제할 수 있다.

@DataRedisTest
class Test {

    @Autowired
    RedisTemplate redisTemplate;

    @Test
    public void test() {
        ListOperations<String, String> operations =  redisTemplate.opsForList();
        operations.rightPush("players", "Ronaldo");
        operations.rightPushAll("players", "Messi", "Benzema", "Suarez");
        List<String> players = operations.range("players", 0, -1);  // ["Ronaldo", "Messi", "Benzema", "Suarez"]
        assertThat(players.size()).isEqualTo(4);
    }
}

# opsForSet()

opsForSet()를 사용하면 Redis Set 타입의 데이터를 저장, 변경, 조회, 삭제할 수 있다.

@DataRedisTest
class Test {

    @Autowired
    RedisTemplate redisTemplate;

    @Test
    public void test() {
        SetOperations<String, String> operations =  redisTemplate.opsForSet();
        operations.add("players", "Paul");
        operations.add("players", "Paul", "John", "Mike", "Monica");
        Set<String> players =  operations.members("players");
        assertThat(players.size()).isEqualTo(4);
    }
}

# opsForHash()

opsForSet()를 사용하면 Redis Hash 타입의 데이터를 저장, 변경, 조회, 삭제할 수 있다.

@DataRedisTest
class Test {

    @Autowired
    RedisTemplate redisTemplate;

    @Test
    public void test() {
        HashOperations<String, String, String> operations =  redisTemplate.opsForHash();
        operations.put("team", "name", "Manchester United");
        operations.put("team", "manager", "Ferguson");
        operations.put("team", "ceo", "John");

        String name = operations.get("team", "name");   // Manchester United
        assertThat(name).isEqualTo("Manchester United");
    }
}

# Serializer

opsForValue()를 사용하여 Redis String 타입의 데이터를 저장해보자.

@DataRedisTest
class Test {

    @Autowired
    RedisTemplate redisTemplate;

    @Test
    public void test() {
        ValueOperations<String, String> operation = redisTemplate.opsForValue();
        operation.set("name", "Cristiano Ronaldo");

        String saved = operation.get("name");   // "Cristiano Ronaldo"
        assertThat(saved).isEqualTo("Cristiano Ronaldo");
    }
}

그리고 redis-cli에서 데이터를 조회해보자.

> KEYS *
1) "\xac\xed\x00\x05t\x00\x04name"

RedisTemplate은 Java 데이터를 바이너리 데이터로 직렬화하여 저장한다. 이때 별도의 설정을 하지 않으면 JdkSerializationRedisSerializer를 사용하여 직렬화를 하는데 이 시리얼라이저는 자바 클래스와 필드 정보를 앞에 붙이게 된다. 키 값인 name 앞에 붙은 \xac\xed\x00\x05t\x00\x04가 바로 이 정보다.

Java 어플리케이션에서 다시 역직렬화 하는데에는 문제가 없지만 다른 시리얼라이저를 사용할 수도 있다. StringRedisSerializer를 사용하면 자바 클래스와 필드 같은 정보를 붙이지 않고 직렬화하게된다.









 
 









@DataRedisTest
class Test {

    @Autowired
    RedisTemplate redisTemplate;

    @Test
    public void test() {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());

        ValueOperations<String, String> operation = redisTemplate.opsForValue();
        operation.set("name", "Cristiano Ronaldo");

        String saved = operation.get("name");   // "Cristiano Ronaldo"
        assertThat(saved).isEqualTo("Cristiano Ronaldo");
    }
}
> KEYS *
1) "name"

보통 Redis 구성 클래스에서 다음과 같이 Serializer를 설정한다.



















 
 




@Configuration
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(host, port);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

# RedisRepository

RedisRepository를 사용하면 더욱 추상화된 방식으로 Redis에 접근할 수 있다. RedisRepository를 사용하려면 설정파일에 @EnableRedisRepositories 어노테이션을 추가해야한다.


 























@Configuration
@EnableRedisRepositories
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(host, port);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

그리고 @RedisHash 어노테이션을 추가한 데이터 클래스를 생성한다.

import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;

@RedisHash("person")
@Getter
public class Person {
    @Id
    String id;
    String email;
    String name;
    String password;

    @Builder
    public Person(String email, String name, String password) {
        this.email = email;
        this.name = name;
        this.password = password;
    }
}

그 다음 Repository를 정의한다.

public interface PersonRepository extends CrudRepository<Person, Long> {
}

이제 다음과 같이 사용할 수 있다.

@DataRedisTest
class Test {

    @Autowired
    PersonRepository personRepository;

    @Test
    public void test() {
        Person p1 = Person.builder()
                .email("Paul@gmail.com")
                .name("Paul")
                .password("1234")
                .build();

        personRepository.save(p1);

        Person p2 = Person.builder()
                .email("Smith@gmail.com")
                .name("Smith")
                .password("1234")
                .build();

        personRepository.save(p2);

        assertThat(personRepository.count()).isEqualTo(2);
    }
}

레디스 서버에서 실제 저장된 데이터를 조회해보자.

> KEYS *
1) "person:a4be06b1-b445-42fe-a70f-0fdee111cf55"
2) "person"
3) "person:4c6d25c4-6d28-44e8-a0ac-434b2106ebd9"

데이터의 타입을 확인해보자.

> TYPE person
set

> TYPE person:a4be06b1-b445-42fe-a70f-0fdee111cf55
hash

> TYPE person:4c6d25c4-6d28-44e8-a0ac-434b2106ebd9
hash

@RedisHash("person") 어노테이션을 붙인 데이터 클래스는 Redis의 Set 타입으로 저장된다.

> smembers person
1) "a4be06b1-b445-42fe-a70f-0fdee111cf55"
2) "4c6d25c4-6d28-44e8-a0ac-434b2106ebd9"

그리고 실제 객체들은 Redis의 Hash 타입으로 저장된다.

> hgetall person:a4be06b1-b445-42fe-a70f-0fdee111cf55
 1) "_class"
 2) "com.yologger.spring_redis.repository.Person"
 3) "email"
 4) "Smith@gmail.com"
 5) "id"
 6) "a4be06b1-b445-42fe-a70f-0fdee111cf55"
 7) "name"
 8) "Smith"
 9) "password"
10) "1234"
> hgetall person:4c6d25c4-6d28-44e8-a0ac-434b2106ebd9
 1) "_class"
 2) "com.yologger.spring_redis.repository.Person"
 3) "email"
 4) "Paul@gmail.com"
 5) "id"
 6) "4c6d25c4-6d28-44e8-a0ac-434b2106ebd9"
 7) "name"
 8) "Paul"
 9) "password"
10) "1234"